//
//
#ifndef Vector2D_h__
#define Vector2D_h__

#include "Gmath.h"
#include "Point2D.h"

namespace Geometric
{
	// Vector 2D
	class Vector2D
	{
	public:
		// vector: (x,y)
		double m_dir_x;
		double m_dir_y;

	public:
		Vector2D(double _dir_x, double _dir_y) 
			:m_dir_x(_dir_x), m_dir_y(_dir_y)
		{
		}

		Vector2D(const Point2D& _start, const Point2D& _end)
			:m_dir_x(_end.m_x - _start.m_x), m_dir_y(_end.m_y - _start.m_y)
		{
		}

		Vector2D(const Vector2D& _vector) 
			:m_dir_x(_vector.m_dir_x), m_dir_y(_vector.m_dir_y)
		{
		}

		Vector2D() :m_dir_x(0), m_dir_y(0)
		{
		}

		~Vector2D()
		{
		}

	public:
		inline Vector2D& operator = (const Vector2D& _vector)
		{
			this->m_dir_x = _vector.m_dir_x;
			this->m_dir_y = _vector.m_dir_y;
			return (*this);
		}

	public:
		// set the value of the vector
		inline void set(double _dir_x, double _dir_y)
		{
			this->m_dir_x = _dir_x;
			this->m_dir_y = _dir_y;
		}

		inline void set(const Vector2D& _vector)
		{
			set(_vector.m_dir_x, _vector.m_dir_y);
		}

		inline void set(const Point2D& _start, const Point2D& _end)
		{
			m_dir_x = _end.m_x - _start.m_x;
			m_dir_y = _end.m_y - _start.m_y;
		}

		// set the value according to the two vectors, this = _vec1 + _vec2
		inline void add(const Vector2D& _vec1, const Vector2D& _vec2)
		{
			m_dir_x = _vec1.m_dir_x + _vec2.m_dir_x;
			m_dir_y = _vec1.m_dir_y + _vec2.m_dir_y;
		}

		// set the value according to the two vectors, this = _vec1 - _vec2
		inline void subtract(const Vector2D& _vec1, const Vector2D& _vec2)
		{
			m_dir_x = _vec1.m_dir_x - _vec2.m_dir_x;
			m_dir_y = _vec1.m_dir_y - _vec2.m_dir_y;
		}

		// get dot product
		inline double getDot(double _dir_x, double _dir_y)
		{
			return (m_dir_x * _dir_x + m_dir_y * _dir_y);
		}

		inline double getDot(const Vector2D& _vec)
		{
			return (m_dir_x * _vec.m_dir_x + m_dir_y * _vec.m_dir_y);
		}

		// get cross product
		inline double getCross(double _dir_x, double _dir_y)
		{
			return (m_dir_x * _dir_y - m_dir_y * _dir_x);
		}

		inline double getCross(const Vector2D& _vec)
		{
			return (m_dir_x * _vec.m_dir_y - m_dir_y * _vec.m_dir_x);
		}

		// get the length of current vector
		inline double getLength()
		{
			return Gmath::toSqrt(m_dir_x * m_dir_x + m_dir_y * m_dir_y);
		}

		// get the square of current vector
		inline double getSquare()
		{
			return (m_dir_x * m_dir_x + m_dir_y * m_dir_y);
		}

		// if current vector is zero-vector
		inline bool isZeroVector()
		{
			return (Gmath::isZero(m_dir_x) && Gmath::isZero(m_dir_y));
		}

		// normalize the length of the vector
		inline double normalizeLength(double _unit = 1)
		{
			double length = Gmath::toSqrt(m_dir_x * m_dir_x + m_dir_y * m_dir_y);
			if(Gmath::isZero(length) == false)
			{
				m_dir_x *= _unit / length;
				m_dir_y *= _unit / length;
			}
			return length;
		}

		// if the two vector is parallel
		inline bool isParallel(double _dir_x, double _dir_y)
		{
			double cross = getCross(_dir_x, _dir_y);
			return Gmath::isZero(cross);
		}

		inline bool isParallel(const Vector2D& _vec)
		{
			return isParallel(_vec.m_dir_x, _vec.m_dir_y);
		}

		// if the two vector is vertical
		inline bool isVertical(double _dir_x, double _dir_y)
		{
			double cross = getDot(_dir_x, _dir_y);
			return Gmath::isZero(cross);
		}

		inline bool isVertical(const Vector2D& _vec)
		{
			return isVertical(_vec.m_dir_x, _vec.m_dir_y);
		}

		// get the clockwise of the two vector
		inline int getClockwise(double _dir_x, double _dir_y)
		{
			double cross = m_dir_x * _dir_y - m_dir_y * _dir_x;
			if(Gmath::isZero(cross) == false)
			{
				return (Gmath::isLess(cross, 0) == true 
					? VECTOR_CLW_FORWARD : VECTOR_CLW_REVERSE);
			}
			return VECTOR_CLW_PARALLEL;
		}

		inline int getClockwise(const Vector2D& _vec)
		{
			return getClockwise(_vec.m_dir_x, _vec.m_dir_y);
		}

		// get the angle of the two vector
		// note: the return value is less than PI
		inline double getAngle(double _dir_x, double _dir_y)
		{
			Vector2D vec1(_dir_x, _dir_y);
			Vector2D vec2(m_dir_x, m_dir_y);

			double len1 = vec1.getLength();
			double len2 = vec2.getLength();
			double result = getDot(_dir_x, _dir_y);
			if(Gmath::isZero(len1) == false && Gmath::isZero(len2) == false)
			{
				result = result / (len1 * len2);
				return ::acos(result);
			}
			return (-1);
		}

		inline double getAngle(const Vector2D& _vec)
		{
			return getAngle(_vec.m_dir_x, _vec.m_dir_y);
		}

		// get the rotate angle from v1 to v2 with clockwise
		inline double getRotateAngle(double _dir_x, double _dir_y)
		{
			double angle = getAngle(_dir_x, _dir_y);
			int clw = getClockwise(_dir_x, _dir_y);

			if(clw == VECTOR_CLW_FORWARD)
			{
				return angle;
			}
			else
			{
				// include parallel and counterclockwise
				return (MATH_CONST_2PI - angle);
			}
		}

		inline double getRotateAngle(const Vector2D& _vec, int _clockwise = VECTOR_CLW_FORWARD)
		{
			double angle = getRotateAngle(_vec.m_dir_x, _vec.m_dir_y);
			return (_clockwise == VECTOR_CLW_FORWARD) ? angle : (MATH_CONST_2PI - angle);
		}

		// if the two vector are the same direction
		inline bool isSameDirection(double _dir_x, double _dir_y)
		{
			if(isParallel(_dir_x, _dir_y) 
				&& Gmath::isSameSign(m_dir_x, _dir_x))
			{
				return true;
			}

			return false;
		}

		inline int isSameDirection(const Vector2D& _vec)
		{
			return isSameDirection(_vec.m_dir_x, _vec.m_dir_y);
		}

		// if the two vector are equal
		inline bool isEqual(const Vector2D& _vec, double epsilon)
		{
			return (Gmath::isEqual(m_dir_x, _vec.m_dir_x, epsilon) 
				&& Gmath::isEqual(m_dir_y, _vec.m_dir_y, epsilon));
		}

		inline bool isLocateBothSides(const Vector2D& _vec1, const Vector2D& _vec2)
		{
			double result = getCross(_vec1)	* getCross(_vec2);
			return Gmath::isLess(result, 0);
		}

		inline virtual void translate(double _x, double _y)
		{
			return;
		}

		inline void rotate(double _angle)
		{
			double cos_angle = ::cos(_angle);
			double sin_angle = ::sin(_angle);
			double tmp_x = m_dir_x;
			double tmp_y = m_dir_y;
			m_dir_x = cos_angle*tmp_x - sin_angle*tmp_y;
			m_dir_y = sin_angle*tmp_x + cos_angle*tmp_y;
		}

		inline virtual void scale(double _x, double _y)
		{
			m_dir_x *= _x;
			m_dir_y *= _y;
		}

	};


}

#endif